package com.xuecheng.content.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.xuecheng.api.content.model.CourseBaseDTO;

import com.xuecheng.api.content.model.TeachplanDTO;
import com.xuecheng.api.content.model.TeachplanMediaDTO;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.common.AuditEnum;
import com.xuecheng.common.enums.common.CommonEnum;
import com.xuecheng.common.enums.content.CourseAuditEnum;
import com.xuecheng.common.enums.content.CourseModeEnum;
import com.xuecheng.common.enums.content.TeachPlanEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.content.agent.MediaApiAgent;
import com.xuecheng.content.common.constant.ContentErrorCode;
import com.xuecheng.content.convert.TeachplanConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.Teachplan;
import com.xuecheng.content.entity.TeachplanMedia;
import com.xuecheng.content.entity.ext.TeachplanNode;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.service.CourseBaseService;
import com.xuecheng.content.service.TeachplanMediaService;
import com.xuecheng.content.service.TeachplanService;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {
    @Autowired
    private  CourseBaseService courseBaseService;
    @Autowired
    private TeachplanMediaService teachplanMediaService;
    /*
        * 业务分析:
        *   1 判断关键数据:操作时的必要数据(一般由前端传过来)
        *           courseId  companyId
        *   2 判断业务数据:操作时,判断数据的状态(一般时判断数据值,数据值来源与后端的数据源)
        *           课程基本信息:
        *               判断是否是同一家机构
        *               判断课程是否删除
        *   3 获得课程下的所有课程计划数据
        *   4 通过Java的递归来生成课程计划的树形结构
        *   5 node数据转化为dto数据并返回
        * */
    public TeachplanDTO queryTreeNodesByCourseId(Long courseId, Long companyId) {

        //1 判断关键数据
        if (ObjectUtils.isEmpty(courseId) || (ObjectUtils.isEmpty(companyId))) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2 判断业务数据
        courseBaseService.getByCourseId(courseId,companyId);
        //3 获得课程下的所有课程计划数据
        TeachplanMapper baseMapper = this.getBaseMapper();
        List<TeachplanNode> nodes = baseMapper.selectTreeNodesByCourseId(courseId);
        //判断是否由课程计划
        if (CollectionUtils.isEmpty(nodes)) {
            return new TeachplanDTO();
        }
        //有课程则递归生成树形结构
        TeachplanNode rootNode = nodes.remove(0);
        generateTreeNodes(rootNode,nodes);
        //将生成的数据转化成dto并返回
        return TeachplanConvert.INSTANCE.node2dto(rootNode);
    }
    //递归生成树形结构方法
    private void generateTreeNodes(TeachplanNode rootNode, List<TeachplanNode> nodes) {
        if (CollectionUtils.isEmpty(rootNode.getChildrenNodes())) {
            ArrayList<TeachplanNode> childrens = new ArrayList<>();
            rootNode.setChildrenNodes(childrens);
        }
        for (TeachplanNode node : nodes) {
            if (ObjectUtils.nullSafeEquals(rootNode.getId(),node.getParentid())) {
                rootNode.getChildrenNodes().add(node);
                if (!(TeachPlanEnum.THIRD_LEVEL.equals(node.getGrade()))) {
                    generateTreeNodes(node,nodes);
                }
            }
        }
    }
    /*
    *  业务分析:
    *       1 是否需要开启事务
    *               需要
    *       2 判断业务操作:
    *               1 是否使新增
    *                   无teachplanid
    *               2 是否修改
    *                   有teachplanid
    * */
    @Transactional
    public TeachplanDTO createOrModifyTeachplan(TeachplanDTO dto, Long companyId) {
        TeachplanDTO resultDTO = null;
        //判断业务
        //依据数据:dto中的teachplanid是否有
        if (ObjectUtils.isEmpty(dto.getTeachPlanId())) {
            //新增操作
            resultDTO = createTeachplan(dto,companyId);
        }else {
            //修改操作
            resultDTO = modifyTeachplan(dto,companyId);
        }
        return resultDTO;
    }



    /*
     *  业务分析：
     *   1.判断关键数据
     *       pname，parentid，couseid
     *   2.判断业务数据
     *       课程基本信息
                 判断课程基本信息是否是同一家机构
                 判断课程是否删除
                 判断课程审核状态
                     只有审核未通过或未提交才可以操作
     *   3.修改数据并判断结果
     *       判断修改的数据是否存在
     *   4.返回dto结果
     *  */
    //修改操作
    private TeachplanDTO modifyTeachplan(TeachplanDTO dto, Long companyId) {
        //1.判断关键数据
        //     pname，parentid，couseid
        if (StringUtil.isBlank(dto.getPname())) {
            ExceptionCast.cast(ContentErrorCode.E_120413);
        }
        if (ObjectUtils.isEmpty(dto.getParentid())) {
            ExceptionCast.cast(ContentErrorCode.E_120408);
        }
        if (ObjectUtils.isEmpty(dto.getCourseId())) {
            ExceptionCast.cast(ContentErrorCode.E_120404);
        }

        // 2.判断业务数据
        //     课程基本信息
        //         判断课程基本信息是否是同一家机构
        //         判断课程是否删除
        //         判断课程审核状态
        //             只有审核未通过或未提交才可以操作
        CourseBaseDTO coursebase = getCourseBaseDTO(dto, companyId);


        // 3.修改数据并判断结果
        // 判断修改的课程计划是否存在
        Teachplan po = this.getById(dto.getTeachPlanId());

        if (ObjectUtils.isEmpty(po)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }

        // 信息修改的内容：pname 、 is_preview、media_type、starttime、endtime

        LambdaUpdateWrapper<Teachplan> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Teachplan::getPname, dto.getPname());
        updateWrapper.set(Teachplan::getIsPreview, dto.getIsPreview());
        updateWrapper.set(Teachplan::getMediaType, dto.getMediaType());
        updateWrapper.set(Teachplan::getStartTime, dto.getStartTime());
        updateWrapper.set(Teachplan::getEndTime, dto.getEndTime());

        updateWrapper.eq(Teachplan::getId, dto.getTeachPlanId());

        boolean result = this.update(updateWrapper);

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

        // 4.返回dto结果
        Teachplan newPo = this.getById(dto.getTeachPlanId());


        return TeachplanConvert.INSTANCE.entity2dto(newPo);
    }
    /*
     *  业务分析：
     *       1.判断关键数据
     *           pname: 课程计划名称
     *       2.判断业务数据
     *           课程基本信息
     *               判断课程基本信息是否是同一家机构
     *               判断课程是否删除
     *               判断课程审核状态
     *                   只有审核未通过或未提交才可以操作
     *       3.将dto数据转为po数据
     *       4.获得父级节点数据（新添加的数据是没有parentid）
     *           获得parentid
     *           获得父级节点的等级：给新添加的数据赋值grade
     *           获得父级节点的子节点的个数：给新添加的数据赋值orderby
     *
     *           让课程计划和课程关联
     *
     *       5.添加课程计划数据
     *       6.转为dto并返回
     * */
    //新增操作
    private TeachplanDTO createTeachplan(TeachplanDTO dto, Long companyId) {
        //1.判断关键数据
        //     pname: 课程计划名称
        if (StringUtil.isBlank(dto.getPname())) {
            ExceptionCast.cast(ContentErrorCode.E_120413);
        }


        // 2.判断业务数据
        //     课程基本信息
        //         判断课程基本信息是否是同一家机构
        //         判断课程是否删除
        //         判断课程审核状态
        //             只有审核未通过或未提交才可以操作
        CourseBaseDTO courseBase = getCourseBaseDTO(dto, companyId);


        // 3.将dto数据转为po数据
        Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(dto);


        // 4.获得父级节点数据（新添加的数据是没有parentid）
        //     获得parentid
        //     获得父级节点的等级：给新添加的数据赋值grade
        //     获得父级节点的子节点的个数：给新添加的数据赋值orderby
        Teachplan parentNode = getParentNode(teachplan,courseBase);


        Long parentId = parentNode.getId();
        teachplan.setParentid(parentId);
        teachplan.setGrade(parentNode.getGrade()+1);

        // 可以通过 parentid 为条件来进行 sql count
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Teachplan::getParentid, parentId);

        int count = this.count(queryWrapper);
        teachplan.setOrderby(count+1);


        // 让课程计划和课程关联
        teachplan.setCourseId(courseBase.getCourseBaseId());

        // 5.添加课程计划数据
        boolean result = this.save(teachplan);

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }


        // 6.转为dto并返回


        return TeachplanConvert.INSTANCE.entity2dto(teachplan);
    }
    private CourseBaseDTO getCourseBaseDTO(TeachplanDTO dto, Long companyId) {
        CourseBaseDTO courseBase = courseBaseService.getByCourseId(dto.getCourseId(), companyId);

        String auditStatus = courseBase.getAuditStatus();
        if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }
        return courseBase;
    }
    /*
    * 获得课程计划的父级节点
    *
    *   1.课程计划的一级（根级）

        - 数据来源：课程基础信息（CourseBase）
        - 创建时机：在第一次添加二级数据时自动生成
        - 注意事项：一级父级（根级）父级 ID 值为 0

        2.课程计划的二级和三级节点

        - 数据来源：教育机构人员在页面填写的数据
        - 创建时机：教育机构人员提交课程计划数据
        - 注意事项：添加时要校验父级菜单是否存在
          - 二级在添加时
            - 父级不存在（一级）：自动创建并获得获得父级Id值
            - 父级存在（一级）：获得父级Id值
          - 三级在添加时
            - 父级不存在（二级）：抛出异常
            - 父级存在（二级）：获得父级Id值
    *
    *   机构添加的课程计划是能是：二级或三级
    *   判断新增的数据是二级或三级：依据是当前Teachplan 是否有parentId值
    *
    * */
    private Teachplan getParentNode(Teachplan dto, CourseBaseDTO courseBase) {

        // 1.判断新增的数据是二级或三级：依据是当前Teachplan 是否有parentId值
        Long parentid = dto.getParentid();

        if (ObjectUtils.isEmpty(parentid)) {
            // 新增的课程计划是二级数据，需要获得一级课程计划

            // 获得一级课程计划
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getCourseId, courseBase.getCourseBaseId());
            queryWrapper.eq(Teachplan::getParentid, TeachPlanEnum.FIRST_PARENTID_FLAG);

            Teachplan rootNode = this.getOne(queryWrapper);

            if (ObjectUtils.isEmpty(rootNode)) {
                // - 父级不存在（一级）：自动创建并获得获得父级Id值
                rootNode = new Teachplan();
                rootNode.setPname(courseBase.getName());
                rootNode.setParentid(new Long(TeachPlanEnum.FIRST_PARENTID_FLAG));
                rootNode.setGrade(TeachPlanEnum.FIRST_LEVEL);
                rootNode.setDescription(courseBase.getDescription());
                rootNode.setOrderby(TeachPlanEnum.FIRST_LEVEL);
                rootNode.setCourseId(courseBase.getCourseBaseId());

                boolean result = this.save(rootNode);
                if (!result) {
                    ExceptionCast.cast(ContentErrorCode.E_120415);
                }

            }
            // - 父级存在（一级）：获得父级Id值
            return rootNode;
        } else {
            // 新增的课程计划是三级数据，需要获得二级课程计划

            // 获得二级课程计划数据
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getId, parentid);

            Teachplan parentNode = this.getOne(queryWrapper);

            if (ObjectUtils.isEmpty(parentNode)) {
                ExceptionCast.cast(ContentErrorCode.E_120408);
            }
            return parentNode;
        }
    }
    /*
     * 判断课程计划的课程信息数
     * */
      private CourseBase verifyCourseMsg(Long courseBaseId, Long companyId) {
        // 1.判断关键业务数据
        if (ObjectUtils.isEmpty(courseBaseId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }

        // 2.查询课程基本信息是否存在（课程计划是没有独立存在的意义）
        CourseBase courseBase = courseBaseService.getById(courseBaseId);

        if (ObjectUtils.isEmpty(courseBase)) {
            ExceptionCast.cast(ContentErrorCode.E_120013);
        }


        // 3.判断课程信息是否被删除
        if (CommonEnum.DELETE_FLAG.getCodeInt().equals(courseBase.getStatus())) {
            ExceptionCast.cast(ContentErrorCode.E_120018);
        }

        // 4.判断课程是否是同一个教学机构
        if (!(ObjectUtils.nullSafeEquals(courseBase.getCompanyId(), companyId))) {
            ExceptionCast.cast(CommonErrorCode.E_100108);
        }

        return courseBase;
    }
    /*
    * 业务分析：
    *   1.判断关键数据
    *       teachplanId、mediaId,companyId
    *   2.判断业务数据
    *       课程的计划
    *           判断是否是第三级
    *           判断课程类型是否录播（课程基本信息中获得并判断）
    *           判断课程计划是否存在：teachplanId、courseId
    *
    *       课程基本信息
    *           判断是否存在或是否是同一家机构
    *           判断是否删除
    *           判断审核状态：未提交、审核未通过
    *
    *       媒资信息
    *           判断媒资信息是否存在
    *           判断是否是同一家机构
    *           判断媒资审核状态
    *   3.将dto数据转为po数据并保存到teachplanmedia表中
    *       添加或修改
    *           如果存在：
                    对课程计划的媒资信息进行修改：mediaId，mediaFileName
                如果不存在：
                    课程计划和媒资信息的添加
    *
    *   4.判断保存数据的结果并返回
    * */
    @Transactional
    public TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto, Long companyId) {
        //1 判断关键数据
        Long teachplanId = dto.getTeachplanId();
        Long mediaId = dto.getMediaId();
        if (ObjectUtils.isEmpty(teachplanId)||
             ObjectUtils.isEmpty(mediaId) ||
              ObjectUtils.isEmpty(companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }

        //2 判断业务数据
            //2.1 课程计划
        Teachplan teachplan = this.getById(teachplanId);
        if (ObjectUtils.isEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }
        // 判断是否是第三级数据
        Integer grade = teachplan.getGrade();
        if (!(TeachPlanEnum.THIRD_LEVEL.equals(grade))) {
            ExceptionCast.cast(ContentErrorCode.E_120410);
        }
            //2.2 课程基础信息
        Long courseId = teachplan.getCourseId();
        // 判断是否是同一家机构
        CourseBaseDTO courseBaseDTO = courseBaseService.getByCourseId(courseId, companyId);
        //判断审核状态
        String auditStatus = courseBaseDTO.getAuditStatus();
        if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }
        //判断课程类型
        String teachmode = courseBaseDTO.getTeachmode();
        if(!(CourseModeEnum.COURSE_MODE_RECORD_STATUS.getCode().equals(teachmode))){
            ExceptionCast.cast(ContentErrorCode.E_120415);
        }
        //判断媒资信息
        RestResponse<MediaDTO> response = MediaApiAgent.getById(mediaId);

        if (!(response.isSuccessful())) {
            ExceptionCast.castWithCodeAndDesc(response.getCode(),response.getMsg());
        }
        // 判断媒资信息是否存在
        MediaDTO mediaDTO = response.getResult();
        if (ObjectUtils.isEmpty(mediaDTO)) {
            ExceptionCast.cast(ContentErrorCode.E_120417);
        }

        // 判断是否是同一家机构
        if (!(ObjectUtils.nullSafeEquals(mediaDTO.getCompanyId(),companyId))) {
            ExceptionCast.cast(CommonErrorCode.E_403000);
        }

        String mediaAuditStatus = mediaDTO.getAuditStatus();
        // 判断媒资审核状态
        if (!(AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(mediaAuditStatus))) {
            ExceptionCast.cast(ContentErrorCode.E_120418);
        }




        // 3.将dto数据转为po数据并保存到teachplanmedia表中
        //     添加或修改
        //         如果存在：
        //             对课程计划的媒资信息进行修改：mediaId，mediaFileName
        //         如果不存在：
        //             课程计划和媒资信息的添加

        LambdaQueryWrapper<TeachplanMedia> teachplanMediaQueryWrapper = new LambdaQueryWrapper<>();
        teachplanMediaQueryWrapper.eq(TeachplanMedia::getTeachplanId,teachplanId);
        teachplanMediaQueryWrapper.eq(TeachplanMedia::getCourseId,courseId);

        TeachplanMedia teachplanMedia = teachplanMediaService.getOne(teachplanMediaQueryWrapper);

        boolean result = false;

        if (ObjectUtils.isEmpty(teachplanMedia)) {
            // 添加操作
            teachplanMedia = new TeachplanMedia();

            teachplanMedia.setMediaId(mediaId);
            teachplanMedia.setTeachplanId(teachplanId);
            teachplanMedia.setCourseId(courseId);
            teachplanMedia.setMediaFilename(mediaDTO.getFilename());

            result = teachplanMediaService.save(teachplanMedia);



        } else {
            // 修改操作: 改为UpdateWrapper
            teachplanMedia.setMediaFilename(mediaDTO.getFilename());
            teachplanMedia.setMediaId(mediaId);

            result = teachplanMediaService.updateById(teachplanMedia);

        }

        // 4.判断保存数据的结果并返回
        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120411);
        }


        TeachplanMediaDTO resultDTO = TeachplanConvert.INSTANCE.teachplanMedia2teachplanmediaDTO(teachplanMedia);

        return resultDTO;
    }
}
